/*
 * @Author: soso
 * @Date: 2022-02-22 13:58:32
 * @LastEditTime: 2022-03-11 16:43:35
 * @LastEditors: Please set LastEditors
 * @Description: 系统命令处理
 * @FilePath: /gomeshsync/cfile/cfile.go
 */
package cfile

import (
	"errors"
	"io/ioutil"
	"log"
	"os"
	"strconv"
	"sync"
	"sync/atomic"
	"time"

	"github.com/vmihailenco/msgpack"
)

const (
	saveDuration time.Duration = time.Millisecond * 100
)

type Cmdfile struct {
	sync.RWMutex
	path       string
	nextkey    int32
	datalen    int32
	needtosave int32
	keysMap    map[int]int
	datas      []*MCommand

	files map[string]*Cfile

	arch *arch
}

func NewCmdfile(path, pathArch string) *Cmdfile {
	c := &Cmdfile{
		path:    path,
		nextkey: 0,
		datalen: 0,
		keysMap: map[int]int{},
		datas:   []*MCommand{},
		files:   map[string]*Cfile{},

		arch: NewArch(pathArch),
	}
	atomic.StoreInt32(&c.needtosave, 0)
	c.load()
	go c.saveRoutine()
	return c
}

func (c *Cmdfile) load() {
	data, e := ioutil.ReadFile(c.path)
	if e == nil {
		err := msgpack.Unmarshal(data, &c.datas)
		if err != nil {
			return
		}
	}

	for i, ci := range c.datas {
		c.keysMap[ci.Ck] = i
	}

	c.setDataLenAndKey(0)

	// log.Println("init load done! the nextkey is:", c.Nextkey())
}

func (c *Cmdfile) Update(cmd *MCommand) {
	index, ok := c.keysMap[cmd.Ck]
	if !ok {
		panic("key not exist")
	}
	c.datas[index] = cmd
	atomic.StoreInt32(&c.needtosave, 1)
}

func (c *Cmdfile) Add(cmd *MCommand) (err error) {
	// 如果本命令序列已经清空，则允许所有key的数据进入(主命令序列需要检测，临时命令序列无需检测，已经在外部进行检测)
	if c.Len() > 0 && cmd.Ck != int(c.Nextkey()) {
		return errors.New("key should be " + strconv.Itoa(int(c.Nextkey())))
	}

	c.datas = append(c.datas, cmd)

	c.keysMap[cmd.Ck] = len(c.datas) - 1

	c.setDataLenAndKey(1)

	// record next key
	log.Print("main command's next key  is: ", c.Nextkey())

	return nil
}

func (c *Cmdfile) Adds(cmds []*MCommand) {
	// 如果本命令序列已经清空，则允许所有key的数据进入(主命令序列需要检测，临时命令序列无需检测，已经在外部进行检测)
	if c.Len() > 0 {
		backupKey := int(c.Nextkey())
		for _, cmd := range cmds {
			if cmd.Ck != backupKey {
				panic("key should be " + strconv.Itoa(backupKey))
			}
			backupKey += 1
		}
	}

	c.datas = append(c.datas, cmds...)

	for i, cmd := range c.datas {
		c.keysMap[cmd.Ck] = i
	}

	c.setDataLenAndKey(1)
}

func (c *Cmdfile) Get(key int) *MCommand {
	return c.datas[c.keysMap[key]]
}

func (c *Cmdfile) Commands() []*MCommand {
	return c.datas
}

// 从keystart开始取得序列到最后
func (c *Cmdfile) GetCommandsFrom(keystart int) (out []*MCommand) {
	out = []*MCommand{}
	if c.Nextkey() <= int32(keystart) {
		return
	}

	if index, ok := c.keysMap[keystart]; ok {
		out = c.datas[index:]
		return
	}

	out = append(c.arch.getCommandsFrom(keystart), c.datas...)
	return
}

// 去掉重复的命令(path相同)，将最后一条path的命令作为执行命令，不包含已归档命令
func (c *Cmdfile) CommandsOptimized() (out []*MCommand) {
	checker := map[string]bool{}
	for i := c.Len() - 1; i >= 0; i-- {

		cmd := c.datas[i]

		if _, ok := checker[cmd.Path]; !ok {
			if cmd.File != nil && cmd.File.FileSize > 0 {
				c.files[cmd.Path] = cmd.File
				cmd.File.FileMask = BitMapSet(cmd.File.FileMask, 0, BitRemoveAll) // 全位置0
			}
			checker[cmd.Path] = true

			cmd.Done = false
			cmd.DoneTime = 0
		} else {
			cmd.Done = true
			cmd.DoneTime = time.Now().Unix()
		}
	}
	return c.datas
}

// 更新文件掩码
func (c *Cmdfile) UpdateFileMask(filePath string, seqNumber int, setMode int) (err error) {
	if file, ok := c.files[filePath]; ok {
		file.FileMask = BitMapSet(file.FileMask, uint(seqNumber), setMode)
		atomic.StoreInt32(&c.needtosave, 1)
		return nil

	} else {
		return errors.New("find not found")

	}
}

// 更新文件掩码, 1 存在, 0 不存在 , -1 没有该文件
func (c *Cmdfile) CheckFileBlock(filePath string, seqNumber int) int {
	if file, ok := c.files[filePath]; ok {
		if BitMapSetted(file.FileMask, uint(seqNumber)) {
			return 1
		} else {
			return 0
		}
	} else {
		return -1
	}
}

// 排除归档命令，从主命令删除序列
func (c *Cmdfile) deletesCommandsTo(keyend int) (success bool) {
	if keyend == int(c.Nextkey())-1 {
		success = true
		c.Clean()
	} else {
		index, success := c.keysMap[keyend]
		if success {
			c.datas = c.datas[index+1:]

			c.keysMap = map[int]int{}
			for i, ci := range c.datas {
				c.keysMap[ci.Ck] = i
			}

			c.setDataLenAndKey(1)
		} else {
			panic("beyond range of commands")
		}
	}
	return
}

func (c *Cmdfile) setDataLenAndKey(needToSave int) {
	atomic.StoreInt32(&c.datalen, int32(len(c.datas)))
	if c.Len() == 0 {
		atomic.StoreInt32(&c.nextkey, c.arch.lastKey()+1)
	} else {
		atomic.StoreInt32(&c.nextkey, int32(c.datas[c.Len()-1].Ck+1))
	}
	atomic.StoreInt32(&c.needtosave, int32(needToSave))
}

func (c *Cmdfile) Nextkey() int32 {
	return atomic.LoadInt32(&c.nextkey)
}

func (c *Cmdfile) Len() int32 {
	return atomic.LoadInt32(&c.datalen)
}

func (c *Cmdfile) Clean() {
	c.keysMap = map[int]int{}
	c.datas = []*MCommand{}
	c.files = map[string]*Cfile{}
	c.setDataLenAndKey(1)
}

func (c *Cmdfile) saveRoutine() {
	tk := time.NewTicker(saveDuration)
	defer tk.Stop()

	for range tk.C {
		if atomic.LoadInt32(&c.needtosave) == 1 {
			c.saveToDisk()
		}
	}
}

func (c *Cmdfile) saveToDisk() {
	c.Lock()
	defer c.Unlock()

	if c.Len() == 0 {
		_, err := os.Stat(c.path)
		if err == nil {
			os.Remove(c.path)
		}
	} else {
		data, _ := msgpack.Marshal(c.datas)
		ioutil.WriteFile(c.path, data, os.ModePerm)
	}
	atomic.StoreInt32(&c.needtosave, 0)
}

// 归档命令
func (c *Cmdfile) ArchiveCmds(expireDuration time.Duration) {
	c.Lock()
	defer c.Unlock()

	log.Println("arch enter lock")

	endTimeUnix := time.Now().Add(-expireDuration).Unix()

	backupCmds := []*MCommand{}

	keyend := 0
	for _, cmd := range c.datas {
		if cmd.Done && cmd.DoneTime <= endTimeUnix {

			backupCmds = append(backupCmds, cmd)
			keyend = cmd.Ck
		} else {
			// 如果有未完成的命令则退出
			break
		}
	}

	if len(backupCmds) == 0 {
		log.Println("done:no commands")
		return
	}

	log.Println("delete to: ", keyend)
	// 清除归档的缓存
	c.arch.clearCache()

	// 写入归档
	buf, _ := msgpack.Marshal(backupCmds)
	c.arch.rfile.Append([][]byte{buf})

	// 从开始删除命令序列
	c.deletesCommandsTo(keyend)

	log.Println("archieved to key: ", keyend)
}
